/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************
	Author: ShonK
	Project: Kauai
	Reviewed:
	Copyright (c) Microsoft Corporation

	Rich text document and associated DDG, continued

***************************************************************************/
#include "frame.h"
ASSERTNAME


RTCLASS(TRUL)

const long kdxpMax = 0x01000000;

/***************************************************************************
	Character run data.
***************************************************************************/
typedef struct CHRD *PCHRD;
struct CHRD
	{
	long cpLim;
	long cpLimDraw;
	long xpLim;
	long xpLimDraw;
	};


/***************************************************************************
	Character run class. This is used to format a line, draw a line,
	map between cp and xp on a line, etc.
***************************************************************************/
const long kcchMaxChr = 128;
class CHR
	{
	ASSERT

private:
	CHP _chp;
	PAP _pap;
	PTXTB _ptxtb;
	PGNV _pgnv;
	bool _fMustAdvance: 1;
	bool _fBreak: 1;
	bool _fObject: 1;

	long _cpMin;
	long _cpLim;
	long _cpLimFetch;
	long _xpMin;
	long _xpBreak;

	CHRD _chrd;
	CHRD _chrdBop;

	long _dypAscent;
	long _dypDescent;

	achar _rgch[kcchMaxChr];

	bool _FFit(void);
	void _SetToBop(void);
	void _SkipIgnores(void);
	void _DoTab(void);

public:
	void Init(CHP *pchp, PAP *ppap, PTXTB ptxtb, PGNV pgnv,
		long cpMin, long cpLim, long xpBase, long xpLimLine, long xpBreak);

	void GetNextRun(bool fMustAdvance = fFalse);
	bool FBreak(void)
		{ return _fBreak; }
	void GetChrd(PCHRD pchrd, PCHRD pchrdBop = pvNil)
		{
		AssertNilOrVarMem(pchrd);
		AssertNilOrVarMem(pchrdBop);

		if (pvNil != pchrd)
			*pchrd = _chrd;
		if (pvNil != pchrdBop)
			*pchrdBop = _chrdBop;
		}
	long DypAscent(void)
		{ return _dypAscent; }
	long DypDescent(void)
		{ return _dypDescent; }
	long XpMin(void)
		{ return _xpMin; }
	long XpBreak(void)
		{ return _xpBreak; }
	achar *Prgch(void)
		{ return _rgch; }
	long CpMin(void)
		{ return _cpMin; }
	bool FObject(void)
		{ return _fObject; }
	};


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a CHR.
***************************************************************************/
void CHR::AssertValid(ulong grf)
{
	AssertThisMem();
	AssertPo(_ptxtb, 0);
	AssertPo(_pgnv, 0);
}
#endif //DEBUG


/***************************************************************************
	Initialize the CHR.
***************************************************************************/
void CHR::Init(CHP *pchp, PAP *ppap, PTXTB ptxtb, PGNV pgnv,
	long cpMin, long cpLim, long xpBase, long xpLimLine, long xpBreak)
{
	AssertVarMem(pchp);
	AssertVarMem(ppap);
	AssertPo(ptxtb, 0);
	AssertPo(pgnv, 0);
	AssertIn(cpMin, 0, ptxtb->CpMac());
	AssertIn(cpLim, cpMin + 1, ptxtb->CpMac() + 1);
	Assert(xpBase <= xpLimLine, "xpBase > xpLimLine");

	RC rc;

	_chp = *pchp;
	_pap = *ppap;
	_ptxtb = ptxtb;
	_pgnv = pgnv;
	_fBreak = fFalse;

	_cpMin = cpMin;
	_cpLim = cpLim;
	_cpLimFetch = cpMin;
	_xpMin = xpBase;
	_xpBreak = LwMin(xpBreak, xpLimLine);

	// apply any indenting
	if (0 == xpBase)
		{
		switch (_pap.nd)
			{
		case ndFirst:
			if (_ptxtb->FMinPara(_cpMin))
				_xpMin += _pap.dxpTab;
			break;
		case ndRest:
			if (!_ptxtb->FMinPara(_cpMin))
				_xpMin += _pap.dxpTab;
			break;
		case ndAll:
			_xpMin += _pap.dxpTab;
			break;
			}
		}
	if (ndAll == _pap.nd)
		_xpBreak = LwMin(_xpBreak, xpLimLine - _pap.dxpTab);

	_chrd.cpLim = _chrd.cpLimDraw = _cpMin;
	_chrd.xpLim = _chrd.xpLimDraw = _xpMin;
	_chrdBop = _chrd;

	// get the vertical dimensions
	_pgnv->SetFont(_chp.onn, _chp.grfont, _chp.dypFont, tahLeft, tavBaseline);

#ifndef SOC_BUG_1500 // REVIEW shonk: Win95 bug workaround
	// If we don't draw to the _pgnv before getting the metrics, the metrics
	// can be different than after we draw!
	achar ch = kchSpace;
	_pgnv->DrawRgch(&ch, 1, 0, 0);
#endif //!REVIEW

	_pgnv->GetRcFromRgch(&rc, pvNil, 0);
	_dypAscent = LwMax(0, -rc.ypTop - _chp.dypOffset);
	_dypDescent = LwMax(0, rc.ypBottom + _chp.dypOffset);

	AssertThis(0);
}


/***************************************************************************
	Get the next run (within the bounds we were inited with).
***************************************************************************/
void CHR::GetNextRun(bool fMustAdvance)
{
	AssertThis(0);
	ulong grfch;
	achar ch;
	RC rc;

	// Start the next run
	if (FIn(_chrd.cpLim, _cpMin + 1, _cpLimFetch))
		BltPb(_rgch + _chrd.cpLim - _cpMin, _rgch, _cpLimFetch - _chrd.cpLim);
	_cpMin = _chrd.cpLimDraw = _chrd.cpLim;
	_xpMin = _chrd.xpLimDraw = _chrd.xpLim;
	_xpBreak = LwMax(_xpBreak, _xpMin);
	_chrdBop = _chrd;

	if (_cpMin >= _cpLim)
		return;

	// Refill the buffer
	if (_cpLimFetch < _cpLim && _cpLimFetch < _cpMin + kcchMaxChr)
		{
		long cpFetch = LwMax(_cpMin, _cpLimFetch);

		_cpLimFetch = LwMin(kcchMaxChr, _cpLim - _cpMin) + _cpMin;
		_ptxtb->FetchRgch(cpFetch, _cpLimFetch - cpFetch,
			_rgch + cpFetch - _cpMin);
		}

	_fMustAdvance = FPure(fMustAdvance);
	_fBreak = fFalse;
	_fObject = fFalse;

	_pgnv->SetFont(_chp.onn, _chp.grfont, _chp.dypFont, tahLeft, tavBaseline);
	for (;;)
		{
		if (_chrd.cpLim >= _cpLimFetch ||
			(fchIgnore & (grfch = GrfchFromCh(
				ch = _rgch[_chrd.cpLim - _cpMin]))))
			{
			// we're out of characters or this is an ignoreable character -
			// return the run
			if (!_FFit())
				_SetToBop();
			else
				_SkipIgnores();
			break;
			}

		if (grfch & fchTab)
			{
			// handle the string of tabs, then return the run
			_DoTab();
			break;
			}

		if (grfch & fchBreak)
			{
			// This line must break after this character - return the run.
			if (!_FFit())
				{
				_SetToBop();
				break;
				}

			_chrd.cpLim++;
			_SkipIgnores();

			// this is a BOP
			_chrdBop = _chrd;
			_fBreak = fTrue;
			break;
			}

		if (grfch & fchMayBreak)
			{
			_chrd.cpLimDraw = ++_chrd.cpLim;
			if (_FFit())
				{
				// this is a BOP
				_chrdBop = _chrd;
				continue;
				}

			_fBreak = fTrue;
			if (grfch & fchWhiteOverhang)
				{
				// see if everything but this character fits
				long xp = _chrd.xpLim;

				_chrd.cpLimDraw--;
				if (_FFit())
					{
					// fits with the overhang
					_chrd.xpLim = xp;
					_chrdBop = _chrd;
					_SkipIgnores();
					break;
					}
				}

			_SetToBop();
			break;
			}

		if (kchObject == ch)
			{
			// this is an object character
			if (_chrd.cpLim > _cpMin)
				{
				// return the run before processing the object - objects
				// go in their own run.
				if (!_FFit())
					_SetToBop();
				else
					_chrdBop = _chrd;
				break;
				}

			// return just the object (if it really is an object)
			if (!_ptxtb->FGetObjectRc(_chrd.cpLim, _pgnv, &_chp, &rc))
				{
				// treat as a normal character
				_chrd.cpLimDraw = ++_chrd.cpLim;
				continue;
				}

			rc.Offset(0, _chp.dypOffset);
			Assert(!rc.FEmpty() && rc.xpRight >= 0,
				"bad rectangle for the object");

			if (fMustAdvance || _xpMin + rc.xpRight <= _xpBreak)
				{
				_fObject = fTrue;
				_chrd.cpLimDraw = ++_chrd.cpLim;
				_chrd.xpLim = _chrd.xpLimDraw = _xpMin + rc.xpRight;
				_chrdBop = _chrd;

				_dypAscent = LwMax(_dypAscent, -rc.ypTop);
				_dypDescent = LwMax(_dypDescent, rc.ypBottom);

				if (_xpMin + rc.xpRight >= _xpBreak)
					_fBreak = fTrue;
				_SkipIgnores();
				}
			break;
			}

		// normal character
		_chrd.cpLimDraw = ++_chrd.cpLim;
		}

	AssertThis(0);
}


/***************************************************************************
	Test whether everything from _cpMin to _chrd.cpLimDraw fits. Assumes the
	font is set in the _pgnv.
***************************************************************************/
bool CHR::_FFit(void)
{
	AssertThis(0);
	RC rc;

	if (_chrd.cpLimDraw == _cpMin)
		_chrd.xpLim = _chrd.xpLimDraw = _xpMin;
	else
		{
		_pgnv->GetRcFromRgch(&rc, _rgch, _chrd.cpLimDraw - _cpMin);
		_chrd.xpLim = _chrd.xpLimDraw = _xpMin + rc.Dxp();
		}

	return _chrd.xpLimDraw <= _xpBreak;
}


/***************************************************************************
	Set the CHR to the last break opportunity. If there wasn't one and
	_fMustAdvance is true, gobble as many characters as we can, but at least
	one.
***************************************************************************/
void CHR::_SetToBop(void)
{
	AssertThis(0);

	_fBreak = fTrue;
	if (_chrdBop.cpLim <= _cpMin && _fMustAdvance)
		{
		// no break opportunity seen - gobble as many characters as we can,
		// but at least one.
		// do a binary search for the character to break at
		Assert(_chrd.cpLimDraw > _cpMin, "why is _chrd.cpLimDraw == _cpMin?");

		RC rc;
		long ivMin, ivLim, iv;
		long dxp = _xpBreak - _xpMin;

		for (ivMin = 0, ivLim = _chrd.cpLimDraw - _cpMin; ivMin < ivLim; )
			{
			iv = (ivMin + ivLim) / 2 + 1;
			AssertIn(iv, ivMin + 1, ivLim + 1);
			_pgnv->GetRcFromRgch(&rc, _rgch, iv);
			if (rc.Dxp() <= dxp)
				ivMin = iv;
			else
				ivLim = iv - 1;
			}
		AssertIn(ivMin, 0, _chrd.cpLimDraw - _cpMin + 1);
		if (ivMin == 0)
			{
			// nothing fits - use one character
			ivMin = 1;
			}

		// set _chrd.cpLim and _chrd.cpLimDraw, then set the _xp values
		_chrd.cpLim = _chrd.cpLimDraw = _cpMin + ivMin;
		_FFit();
		}
	else
		_chrd = _chrdBop;

	_SkipIgnores();
}


/***************************************************************************
	Skip any trailing ignore characters. Just changes _chrd.cpLim.
***************************************************************************/
void CHR::_SkipIgnores(void)
{
	AssertThis(0);

	while (_chrd.cpLim < _cpLimFetch &&
			(fchIgnore & GrfchFromCh(_rgch[_chrd.cpLim - _cpMin])))
		{
		_chrd.cpLim++;
		}

	if (_chrd.cpLim == _cpLimFetch)
		{
		achar ch;
		long cpMac = _ptxtb->CpMac();

		while (_chrd.cpLim < cpMac)
			{
			_ptxtb->FetchRgch(_chrd.cpLim, 1, &ch);
			if (!(fchIgnore & GrfchFromCh(ch)))
				return;
			_chrd.cpLim++;
			}
		}
}


/***************************************************************************
	Swallow as many tabs as possible.
***************************************************************************/
void CHR::_DoTab(void)
{
	AssertThis(0);

	if (!_FFit())
		{
		_SetToBop();
		return;
		}

	while (_chrd.cpLim < _cpLimFetch &&
			(fchTab & GrfchFromCh(_rgch[_chrd.cpLim - _cpMin])))
		{
		_chrd.cpLim++;
		_chrd.xpLim = LwRoundAway(_chrd.xpLim + 1, _pap.dxpTab);
		if (_chrd.xpLim > _xpBreak)
			{
			// this tab would carry us over the edge.
			if (_chrd.cpLim == _cpMin + 1 && _fMustAdvance)
				{
				// the line is empty, so we have to force the tab onto the line
				_SkipIgnores();
				_fBreak = fTrue;
				}
			else
				_SetToBop();
			return;
			}

		// this is a BOP
		_chrd.xpLimDraw = _chrd.xpLim;
		_chrdBop = _chrd;
		}

	_SkipIgnores();
}


/***************************************************************************
	Constructor for the text document display GOB.
***************************************************************************/
TXTG::TXTG(PTXTB ptxtb, PGCB pgcb) : TXTG_PAR(ptxtb, pgcb)
{
	AssertBaseThis(0);
	_ptxtb = ptxtb;
	_fMark = (kginMark == pgcb->_gin ||
		 kginDefault == pgcb->_gin && kginMark == GOB::GinDefault());
	_pgnv = pvNil;
}


/***************************************************************************
	Destructor for TXTG.
***************************************************************************/
TXTG::~TXTG(void)
{
	AssertBaseThis(0);
	ReleasePpo(&_pgllin);
	ReleasePpo(&_pgnv);
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a TXTG.
***************************************************************************/
void TXTG::AssertValid(ulong grf)
{
	TXTG_PAR::AssertValid(0);
	AssertPo(_pgllin, 0);
	AssertIn(_ilinInval, 0, _pgllin->IvMac() + 1);
	AssertPo(_pgnv, 0);
	AssertNilOrPo(_ptrul, 0);
	//REVIEW shonk: TXTG::AssertValid: fill out.
}


/***************************************************************************
	Mark memory for the TXTG.
***************************************************************************/
void TXTG::MarkMem(void)
{
	AssertValid(0);
	TXTG_PAR::MarkMem();
	MarkMemObj(_pgllin);
	MarkMemObj(_pgnv);
}
#endif //DEBUG


/***************************************************************************
	Initialize the text document display gob.
***************************************************************************/
bool TXTG::_FInit(void)
{
	AssertBaseThis(0);
	PGPT pgpt;

	if (!TXTG_PAR::_FInit())
		return fFalse;
	if (pvNil == (_pgllin = GL::PglNew(size(LIN))))
		return fFalse;

	// Allocate the GNV for formatting. Use an offscreen one iff _fMark
	// is set.
	if (_fMark)
		{
		RC rc(0, 0, 1, 1);

		if (pvNil == (pgpt = GPT::PgptNewOffscreen(&rc, 8)))
			return fFalse;
		}
	else
		{
		pgpt = Pgpt();
		pgpt->AddRef();
		}

	_pgnv = NewObj GNV(this, pgpt);
	ReleasePpo(&pgpt);

	if (pvNil == _pgnv)
		return fFalse;

	_pgllin->SetMinGrow(20);
	_ilinDisp = 0;
	_cpDisp = 0;
	_dypDisp = 0;
	_ilinInval = 0;
	if (_DxpDoc() > 0)
		{
		long cpMac = _ptxtb->CpMac();
		_Reformat(0, cpMac, cpMac);
		}

	AssertThis(0);
	return fTrue;
}


/***************************************************************************
	Deactivate the TXTG - turn off the selection.
***************************************************************************/
void TXTG::_Activate(bool fActive)
{
	AssertThis(0);

	TXTG_PAR::_Activate(fActive);
	if (!fActive)
		_SwitchSel(fFalse, kginSysInval);
}


/***************************************************************************
	Get the LIN for the given ilin.  If ilin is past the end of _pgllin,
	_CalcLine is called repeatedly and new lines are added to _pgllin.
	The actual index of the returned line is put in *pilinActual (if not nil).
***************************************************************************/
void TXTG::_FetchLin(long ilin, LIN *plin, long *pilinActual)
{
	AssertThis(0);
	AssertIn(ilin, 0, kcbMax);
	AssertVarMem(plin);
	AssertNilOrVarMem(pilinActual);

	long cpLim, cpMac;
	long dypTot;
	LIN *qlin;
	bool fAdd;
	long ilinLim = LwMin(_pgllin->IvMac(), ilin + 1);

	if (pvNil != pilinActual)
		*pilinActual = ilin;

	if (_ilinInval < ilinLim)
		{
		qlin = (LIN *)_pgllin->QvGet(_ilinInval);
		if (_ilinInval == 0)
			{
			cpLim = 0;
			dypTot = 0;
			}
		else
			{
			qlin--;
			cpLim = qlin->cpMin + qlin->ccp;
			dypTot = qlin->dypTot + qlin->dyp;
			qlin++;
			}

		// adjust LINs up to ilinLim
		for ( ; _ilinInval < ilinLim; _ilinInval++, qlin++)
			{
			qlin->cpMin = cpLim;
			qlin->dypTot = dypTot;
			cpLim += qlin->ccp;
			dypTot += qlin->dyp;
			}
		}
	Assert(_ilinInval >= ilinLim, 0);

	if (ilin < _ilinInval)
		{
		*plin = *(LIN *)_pgllin->QvGet(ilin);
		return;
		}

	Assert(ilinLim == _pgllin->IvMac(), 0);
	if (ilinLim == 0)
		{
		cpLim = 0;
		dypTot = 0;
		ClearPb(plin, size(LIN));
		}
	else
		{
		// get the LIN in case we don't actuall calc any lines below
		*plin = *(LIN *)_pgllin->QvGet(ilinLim - 1);
		cpLim = plin->cpMin + plin->ccp;
		dypTot = plin->dypTot + plin->dyp;
		}

	cpMac = _ptxtb->CpMac();
	fAdd = fTrue;
	for ( ; ilinLim <= ilin && cpLim < cpMac; ilinLim++)
		{
		_CalcLine(cpLim, dypTot, plin);
		fAdd = fAdd && _pgllin->FAdd(plin);
		cpLim += plin->ccp;
		dypTot += plin->dyp;
		}
	_ilinInval = _pgllin->IvMac();
	if (pvNil != pilinActual)
		*pilinActual = ilinLim - 1;
}


/***************************************************************************
	Find the LIN that contains the given cpFind. pilin and/or plin can be nil.
	If fCalcLines is false, we won't calculate any new lines and the
	returned LIN may be before cpFind.
***************************************************************************/
void TXTG::_FindCp(long cpFind, LIN *plin, long *pilin, bool fCalcLines)
{
	AssertThis(0);
	AssertIn(cpFind, 0, _ptxtb->CpMac());
	AssertNilOrVarMem(pilin);
	AssertNilOrVarMem(plin);

	LIN *qlin;
	LIN lin;
	long dypTot;
	long cpLim;
	long ilinMac;
	bool fAdd;

	// get the starting cp and dypTot values for _ilinInval
	qlin = (LIN *)_pgllin->QvGet(_ilinInval);
	if (_ilinInval == 0)
		{
		cpLim = 0;
		dypTot = 0;
		}
	else
		{
		qlin--;
		cpLim = qlin->cpMin + qlin->ccp;
		dypTot = qlin->dypTot + qlin->dyp;
		qlin++;
		}

	if (cpFind < cpLim)
		{
		// do a binary search to find the LIN containing cpFind
		long ivMin, ivLim, iv;

		for (ivMin = 0, ivLim = _ilinInval; ivMin < ivLim; )
			{
			iv = (ivMin + ivLim) / 2;
			qlin = (LIN *)_pgllin->QvGet(iv);
			if (cpFind < qlin->cpMin)
				ivLim = iv;
			else if (cpFind >= qlin->cpMin + qlin->ccp)
				ivMin = iv + 1;
			else
				{
				if (pvNil != pilin)
					*pilin = iv;
				if (pvNil != plin)
					*plin = *qlin;
				return;
				}
			}

		Bug("Invalid LINs");
		cpLim = 0;
		dypTot = 0;
		_ilinInval = 0;
		}

	Assert(cpFind >= cpLim, "why isn't cpFind >= cpLim?");
	if (_ilinInval < (ilinMac = _pgllin->IvMac()))
		{
		// adjust LINs up to cpFind
		qlin = (LIN *)_pgllin->QvGet(_ilinInval);
		for ( ; _ilinInval < ilinMac && cpFind >= cpLim; _ilinInval++, qlin++)
			{
			qlin->cpMin = cpLim;
			qlin->dypTot = dypTot;
			cpLim += qlin->ccp;
			dypTot += qlin->dyp;
			}

		if (cpFind < cpLim)
			{
			AssertIn(cpFind, qlin[-1].cpMin, cpLim);
			if (pvNil != pilin)
				*pilin = _ilinInval - 1;
			if (pvNil != plin)
				*plin = qlin[-1];
			return;
			}
		}
	Assert(_ilinInval == ilinMac, "why isn't _ilinInval == ilinMac?");
	Assert(cpFind >= cpLim, "why isn't cpFind >= cpLim?");

	if (!fCalcLines)
		{
		if (pvNil != pilin)
			*pilin = ilinMac - (ilinMac > 0);
		if (pvNil != plin)
			{
			if (ilinMac > 0)
				*plin = *(LIN *)_pgllin->QvGet(ilinMac - 1);
			else
				ClearPb(plin, size(LIN));
			}
		return;
		}

	// have to calculate some lines
	for (fAdd = fTrue; cpFind >= cpLim; ilinMac++)
		{
		_CalcLine(cpLim, dypTot, &lin);
		fAdd = fAdd && _pgllin->FAdd(&lin);
		cpLim += lin.ccp;
		dypTot += lin.dyp;
		}
	_ilinInval = _pgllin->IvMac();

	if (pvNil != pilin)
		*pilin = ilinMac - 1;
	if (pvNil != plin)
		*plin = lin;
}


/***************************************************************************
	Find the LIN that contains the given dypFind value (measured from the top
	of the document). pilin and/or plin can be nil.
	If fCalcLines is false, we won't calculate any new lines and the
	returned LIN may be before dypFind.
***************************************************************************/
void TXTG::_FindDyp(long dypFind, LIN *plin, long *pilin, bool fCalcLines)
{
	AssertThis(0);
	AssertIn(dypFind, 0, kcbMax);
	AssertNilOrVarMem(pilin);
	AssertNilOrVarMem(plin);

	LIN *qlin;
	LIN lin;
	long dypTot;
	long cpLim, cpMac;
	long ilinMac;
	bool fAdd;

	// get the starting cp and dypTot values for _ilinInval
	qlin = (LIN *)_pgllin->QvGet(_ilinInval);
	if (_ilinInval == 0)
		{
		cpLim = 0;
		dypTot = 0;
		}
	else
		{
		qlin--;
		cpLim = qlin->cpMin + qlin->ccp;
		dypTot = qlin->dypTot + qlin->dyp;
		qlin++;
		}

	if (dypFind < dypTot)
		{
		// do a binary search to find the LIN containing dypFind
		long ivMin, ivLim, iv;

		for (ivMin = 0, ivLim = _ilinInval; ivMin < ivLim; )
			{
			iv = (ivMin + ivLim) / 2;
			qlin = (LIN *)_pgllin->QvGet(iv);
			if (dypFind < qlin->dypTot)
				ivLim = iv;
			else if (dypFind >= qlin->dypTot + qlin->dyp)
				ivMin = iv + 1;
			else
				{
				if (pvNil != pilin)
					*pilin = iv;
				if (pvNil != plin)
					*plin = *qlin;
				return;
				}
			}

		Bug("Invalid LINs");
		cpLim = 0;
		dypTot = 0;
		_ilinInval = 0;
		}

	Assert(dypFind >= dypTot, "why isn't dypFind >= dypTot?");
	if (_ilinInval < (ilinMac = _pgllin->IvMac()))
		{
		// adjust LINs up to cp
		qlin = (LIN *)_pgllin->QvGet(_ilinInval);
		for ( ; _ilinInval < ilinMac && dypFind >= dypTot; _ilinInval++, qlin++)
			{
			qlin->cpMin = cpLim;
			qlin->dypTot = dypTot;
			cpLim += qlin->ccp;
			dypTot += qlin->dyp;
			}

		if (dypFind < dypTot)
			{
			AssertIn(dypFind, qlin[-1].dypTot, dypTot);
			if (pvNil != pilin)
				*pilin = _ilinInval - 1;
			if (pvNil != plin)
				*plin = qlin[-1];
			return;
			}
		}
	Assert(_ilinInval == ilinMac, "why isn't _ilinInval == ilinMac?");
	Assert(dypFind >= dypTot, "why isn't dypFind >= dypTot?");

	if (!fCalcLines)
		{
		if (pvNil != pilin)
			*pilin = ilinMac - (ilinMac > 0);
		if (pvNil != plin)
			{
			if (ilinMac > 0)
				*plin = *(LIN *)_pgllin->QvGet(ilinMac - 1);
			else
				ClearPb(plin, size(LIN));
			}
		return;
		}

	cpMac = _ptxtb->CpMac();
	if (cpLim >= cpMac)
		{
		if (pvNil != plin)
			{
			// Get a valid lin.
			if (ilinMac > 0)
				_pgllin->Get(ilinMac - 1, &lin);
			else
				ClearPb(&lin, size(LIN));
			}
		_ilinInval = ilinMac;
		}
	else
		{
		Assert(dypFind >= dypTot && cpLim < cpMac, 0);
		for (fAdd = fTrue; dypFind >= dypTot && cpLim < cpMac; ilinMac++)
			{
			_CalcLine(cpLim, dypTot, &lin);
			fAdd = fAdd && _pgllin->FAdd(&lin);
			cpLim += lin.ccp;
			dypTot += lin.dyp;
			}
		_ilinInval = _pgllin->IvMac();
		}

	if (pvNil != pilin)
		*pilin = ilinMac - 1;
	if (pvNil != plin)
		*plin = lin;
}


/***************************************************************************
	Recalculate the _pgllin after an edit.  Sets *pyp, *pdypIns, *pdypDel
	to indicate the vertical display space that was affected.
***************************************************************************/
void TXTG::_Reformat(long cp, long ccpIns, long ccpDel,
	long *pyp, long *pdypIns, long *pdypDel)
{
	AssertThis(0);
	AssertIn(cp, 0, _ptxtb->CpMac());
	AssertIn(ccpIns, 0, _ptxtb->CpMac() - cp + 1);
	AssertIn(ccpDel, 0, kcbMax);
	AssertNilOrVarMem(pyp);
	AssertNilOrVarMem(pdypIns);
	AssertNilOrVarMem(pdypDel);

	long ypDel, dypDel, dypIns, dypCur;
	long ccp, cpCur, cpNext, cpMac;
	long ilin, ilinOld;
	LIN linOld, lin, linT;

	_fClear = _ptxtb->AcrBack() == kacrClear;
	_fXpValid = fFalse;
	cpMac = _ptxtb->CpMac();

	// Find the LIN that contains cp (if there is one) - don't calc any lines
	// to get the LIN.
	_FindCp(cp, &linOld, &ilinOld, fFalse);

	if (cp >= linOld.cpMin + linOld.ccp)
		{
		// the LIN for this cp was not cached - recalc from the beginning of
		// the last lin.
		ccpIns += cp - linOld.cpMin;
		ccpDel += cp - linOld.cpMin;
		cp = linOld.cpMin;
		}
	AssertIn(cp, linOld.cpMin, linOld.cpMin + linOld.ccp + (linOld.ccp == 0));

	// make sure the previous line is formatted correctly
	if (ilinOld > 0 && !_ptxtb->FMinPara(linOld.cpMin))
		{
		_FetchLin(ilinOld - 1, &lin);
		_CalcLine(lin.cpMin, lin.dypTot, &linT);
		if (linT.ccp != lin.ccp)
			{
			//edit affected previous line - so start
			//formatting from there
			ilinOld--;
			linOld = lin;
			}
		}
	AssertIn(cp, linOld.cpMin, cpMac);

	// remove deleted lines
	Assert(ilinOld <= _ilinInval, 0);
	_ilinInval = ilinOld;
	for (ccp = dypDel = 0;
			linOld.cpMin + ccp <= cp + ccpDel && ilinOld < _pgllin->IvMac(); )
		{
		_pgllin->Get(ilinOld, &lin);
		dypDel += lin.dyp;
		ccp += lin.ccp;
		_pgllin->Delete(ilinOld);
		}

	//insert the new lines
	cpCur = linOld.cpMin;
	dypCur = linOld.dypTot;

	// cpNext is the cp of the next (possibly stale) LIN in _pgllin
	if (ilinOld < _pgllin->IvMac())
		{
		cpNext = linOld.cpMin + ccp - ccpDel + ccpIns;
		AssertIn(cpNext, linOld.cpMin, cpMac + 1);
		}
	else
		cpNext = cpMac;

	AssertIn(ilinOld, 0, _pgllin->IvMac() + 1);
	dypIns = 0;
	for (ilin = ilinOld; ; )
		{
		AssertIn(cpCur, linOld.cpMin, cpMac + 1);
		AssertIn(dypCur, linOld.dypTot, kcbMax);
		while (cpNext < cpCur && ilin < _pgllin->IvMac())
			{
			_pgllin->Get(ilin, &lin);
			_pgllin->Delete(ilin);
			cpNext += lin.ccp;
			dypDel += lin.dyp;
			}
		AssertIn(cpNext, cpCur, cpMac + 1);

		if (ilin >= _pgllin->IvMac())
			{
			// no more LINs that might be preservable, so we may as well
			// stop trying.
			ilin = _pgllin->IvMac();
			if (cpNext < cpMac)
				dypDel = kswMax;
			if (cpCur < cpMac)
				dypIns = kswMax;
			break;
			}

		AssertIn(ilin, 0, _pgllin->IvMac());
		if (cpCur == cpNext)
			{
			// everything from here on should be correct - we still need to
			// set _ilinDisp
			break;
			}

		_CalcLine(cpCur, dypCur, &lin);
		if (!_pgllin->FInsert(ilin, &lin))
			{
			AssertIn(ilin, 0, _pgllin->IvMac());
			_pgllin->Get(ilin, &linT);
			cpNext += linT.ccp;
			dypDel += linT.dyp;
			_pgllin->Put(ilin, &lin);
			}

		dypIns += lin.dyp;
		cpCur += lin.ccp;
		dypCur += lin.dyp;
		ilin++;
		}
	_ilinInval = ilin;

	if (_cpDisp <= linOld.cpMin)
		ypDel = linOld.dypTot - _dypDisp;
	else
		{
		if (_cpDisp > cp)
			{
			if (_cpDisp >= cp + ccpDel)
				_cpDisp += ccpIns - ccpDel;
			else if (_cpDisp >= cp + ccpIns)
				_cpDisp = cp + ccpIns;
			}
		ypDel = 0;
		_FindCp(_cpDisp, &lin, &_ilinDisp);
		_cpDisp = lin.cpMin;
		dypDel = LwMax(0, linOld.dypTot + dypDel - _dypDisp);
		_dypDisp = lin.dypTot;
		dypIns = LwMax(0, linOld.dypTot + dypIns - _dypDisp);
		}

	if (pvNil != pyp)
		*pyp = ypDel;
	if (pvNil != pdypIns)
		*pdypIns = dypIns;
	if (pvNil != pdypDel)
		*pdypDel = dypDel;
}


/***************************************************************************
	Calculate the end of the line, the left position of the line, the height
	of the line and the ascent of the line.
***************************************************************************/
void TXTG::_CalcLine(long cpMin, long dypBase, LIN *plin)
{
	AssertThis(0);
	AssertIn(cpMin, 0, _ptxtb->CpMac());
	AssertVarMem(plin);

	struct RUN
		{
		long cpLim;
		long xpLim;
		long dypAscent;
		long dypDescent;
		};

	long dxpDoc;
	PAP pap;
	CHP chp;
	long cpLimPap, cpLimChp;
	CHR chr;
	CHRD chrd, chrdBop;
	RUN run, runSure, runT;

	dxpDoc = _DxpDoc();
	_FetchPap(cpMin, &pap, pvNil, &cpLimPap);

	cpLimChp = cpMin;
	run.cpLim = cpMin;
	run.xpLim = 0;
	run.dypAscent = run.dypDescent = 0;
	runSure = run;

	for (;;)
		{
		// make sure the chp is valid
		if (run.cpLim >= cpLimChp)
			{
			_FetchChp(run.cpLim, &chp, pvNil, &cpLimChp);
			cpLimChp = LwMin(cpLimChp, cpLimPap);
			if (cpLimChp <= run.cpLim)
				{
				Bug("why is run.cpLim >= cpLimChp?");
				break;
				}

			chr.Init(&chp, &pap, _ptxtb, _pgnv, run.cpLim, cpLimChp,
				run.xpLim, dxpDoc, dxpDoc);
			}

		chr.GetNextRun(runSure.cpLim == cpMin);
		chr.GetChrd(&chrd, &chrdBop);

		if (chrd.cpLim == run.cpLim)
			{
			// didn't move forward - use runSure
			Assert(runSure.cpLim > cpMin, "why don't we have a BOP?");
			run = runSure;
			break;
			}

		runT = run;
		run.cpLim = chrd.cpLim;
		run.xpLim = chrd.xpLimDraw;
		run.dypAscent = LwMax(run.dypAscent, chr.DypAscent());
		run.dypDescent = LwMax(run.dypDescent, chr.DypDescent());

		if (chr.FBreak())
			{
			// we know that this is the end of the line - use run or runT
			if (run.xpLim > chr.XpBreak() && runT.cpLim > cpMin)
				run = runT;
			break;
			}

		if (chrdBop.cpLim > runT.cpLim)
			{
			// put chrdBop info into runSure
			runSure.cpLim = chrdBop.cpLim;
			runSure.xpLim = chrdBop.xpLimDraw;
			runSure.dypAscent = LwMax(runSure.dypAscent, chr.DypAscent());
			runSure.dypDescent = LwMax(runSure.dypDescent,
				chr.DypDescent());
			}
		}

	Assert(run.dypAscent > 0 || run.dypDescent > 0, "bad run");
	Assert(run.cpLim > cpMin, "why is cch zero?");

	switch (pap.jc)
		{
	default:
		plin->xpLeft = 0;
		break;
	case jcRight:
		plin->xpLeft = (short)(dxpDoc - run.xpLim);
		break;
	case jcCenter:
		plin->xpLeft = (dxpDoc - run.xpLim) / 2;
		break;
		}

	plin->ccp = (short)(run.cpLim - cpMin);
	plin->dyp = (short)(run.dypAscent + run.dypDescent);
	if (pap.numLine != kdenLine)
		plin->dyp = (short)LwMulDiv(plin->dyp, pap.numLine, kdenLine);
	plin->dyp += pap.dypExtraLine;
	if (_ptxtb->FMinPara(run.cpLim) || run.cpLim == _ptxtb->CpMac())
		{
		if (pap.numAfter != kdenAfter)
			plin->dyp = (short)LwMulDiv(plin->dyp, pap.numAfter, kdenAfter);
		plin->dyp += pap.dypExtraAfter;
		}
	if (plin->dyp <= 0)
		plin->dyp = 1;
	plin->dypAscent = (short)run.dypAscent;
	if (plin->dypAscent <= 0)
		plin->dypAscent = 1;
	plin->cpMin = cpMin;
	plin->dypTot = dypBase;
}


/***************************************************************************
	Get the cp that the point is in.  If fClosest is true, this finds the
	cp boundary that the point is closest to (for traditional selection).
	If fClosest is false, it finds the character that the point is over.
***************************************************************************/
bool TXTG::_FGetCpFromPt(long xp, long yp, long *pcp, bool fClosest)
{
	AssertThis(0);
	AssertVarMem(pcp);

	LIN lin;
	RC rc;

	GetRc(&rc, cooLocal);
	if (!FIn(yp, 0, rc.ypBottom))
		return fFalse;

	_FindDyp(_dypDisp + yp, &lin);
	if (_dypDisp + yp >= lin.dypTot + lin.dyp)
		{
		*pcp = _ptxtb->CpMac() - 1;
		return fTrue;
		}

	//we've found the line, now get the cp on the line
	return _FGetCpFromXp(xp, &lin, pcp, fClosest);
}


/***************************************************************************
	Get the cp on the line given by *plin that the xp is in.  If fClosest
	is true, this finds the cp boundary that the point is closest to (for
	traditional selection).  If fClosest is false, it finds the character
	that the xp is over. This only returns false if fClosest is false and
	the xp is before the beginning of the line.
***************************************************************************/
bool TXTG::_FGetCpFromXp(long xp, LIN *plin, long *pcp, bool fClosest)
{
	AssertThis(0);
	AssertVarMem(plin);
	AssertIn(plin->cpMin, 0, _ptxtb->CpMac());
	AssertVarMem(plin);
	AssertIn(plin->ccp, 1, _ptxtb->CpMac() + 1 - plin->cpMin);
	AssertVarMem(pcp);

	CHP chp;
	PAP pap;
	CHR chr;
	long cpCur, cpLimChp, cpLim;
	long xpCur, dxpDoc;
	CHRD chrd;

	xp -= plin->xpLeft + kdxpIndentTxtg - _scvHorz;
	if (xp <= 0)
		{
		*pcp = plin->cpMin;
		return FPure(fClosest);
		}

	if (xp >= (dxpDoc = _DxpDoc()))
		{
		*pcp = _ptxtb->CpPrev(plin->cpMin + plin->ccp);
		return fTrue;
		}

	cpLimChp = cpCur = plin->cpMin;
	cpLim = cpCur + plin->ccp;
	xpCur = 0;
	_FetchPap(cpCur, &pap);

	for (;;)
		{
		// make sure the chp is valid
		if (cpCur >= cpLimChp)
			{
			if (cpCur >= cpLim)
				{
				// everything fit
				*pcp = LwMax(plin->cpMin, _ptxtb->CpPrev(cpLim));
				return fTrue;
				}

			_FetchChp(cpCur, &chp, pvNil, &cpLimChp);
			cpLimChp = LwMin(cpLimChp, cpLim);
			Assert(cpLimChp > cpCur, "why is cpCur >= cpLimChp?");
			chr.Init(&chp, &pap, _ptxtb, _pgnv, cpCur, cpLimChp, xpCur,
				dxpDoc, xp);
			}

		chr.GetNextRun(fTrue);
		chr.GetChrd(&chrd);

		if (chr.FBreak() &&
				(chrd.xpLim >= chr.XpBreak() || chrd.cpLim >= cpLim))
			{
			long cpPrev = _ptxtb->CpPrev(chrd.cpLim);

			if (!fClosest || chrd.cpLim >= cpLim)
				goto LPrev;

			if (cpPrev > cpCur)
				{
				CHRD chrdT;

				// get the length from cpCur to cpPrev
				chr.Init(&chp, &pap, _ptxtb, _pgnv, cpCur, cpPrev, xpCur,
					dxpDoc, xp);
				chr.GetNextRun(fTrue);
				chr.GetChrd(&chrdT);
				cpCur = chrdT.cpLim;
				xpCur = chrdT.xpLim;
				}

			Assert(xp >= xpCur && chrd.xpLim >= xp, "what?");
			if (xp - xpCur > chrd.xpLim - xp)
				{
				*pcp = chrd.cpLim;
				return fTrue;
				}
LPrev:
			*pcp = LwMax(plin->cpMin, cpPrev);
			return fTrue;
			}
		xpCur = chrd.xpLim;
		cpCur = chrd.cpLim;
		}
}


/***************************************************************************
	Get the vertical bounds of the line containing cp and the horizontal
	position of the cp on the line.  If fView is true, the values are in
	view coordinates. If fView is false, the values are in logical values
	(independent of the current scrolling of the view).
***************************************************************************/
void TXTG::_GetXpYpFromCp(long cp, long *pypMin, long *pypLim, long *pxp,
	long *pypBaseLine, bool fView)
{
	AssertThis(0);
	AssertIn(cp, 0, _ptxtb->CpMac());
	AssertNilOrVarMem(pypMin);
	AssertNilOrVarMem(pypLim);
	AssertNilOrVarMem(pxp);
	AssertNilOrVarMem(pypBaseLine);

	LIN lin;
	long xp;

	_FindCp(cp, &lin);
	xp = lin.xpLeft;
	if (fView)
		{
		lin.dypTot -= _dypDisp;
		xp -= _scvHorz;
		}

	if (pvNil != pypMin)
		*pypMin = lin.dypTot;
	if (pvNil != pypLim)
		*pypLim = lin.dypTot + lin.dyp;
	if (pvNil != pxp)
		*pxp = xp + _DxpFromCp(lin.cpMin, cp);
	if (pvNil != pypBaseLine)
		*pypBaseLine = lin.dypTot + lin.dypAscent;
}


/***************************************************************************
	Find the xp location of the given cp. Assumes that cpLine is the start
	of the line containing cp. This includes a buffer on the left of
	kdxpIndentTxtg, but doesn't include centering or right justification
	correction.
***************************************************************************/
long TXTG::_DxpFromCp(long cpLine, long cp)
{
	AssertThis(0);
	AssertIn(cpLine, 0, _ptxtb->CpMac());
	AssertIn(cp, cpLine, _ptxtb->CpMac());

	CHP chp;
	PAP pap;
	CHR chr;
	long cpCur, cpLimChp, cpLim;
	long xpCur;
	CHRD chrd;

	cpLimChp = cpCur = cpLine;
	cpLim = cp + (cp == cpLine);
	xpCur = 0;
	_FetchPap(cpCur, &pap);

	for (;;)
		{
		// make sure the chp is valid
		if (cpCur >= cpLimChp)
			{
			_FetchChp(cpCur, &chp, pvNil, &cpLimChp);
			cpLimChp = LwMin(cpLimChp, cpLim);
			Assert(cpLimChp > cpCur, "why is cpCur >= cpLimChp?");
			chr.Init(&chp, &pap, _ptxtb, _pgnv, cpCur, cpLimChp,
				xpCur, kdxpMax, kdxpMax);
			}

		chr.GetNextRun();
		chr.GetChrd(&chrd);

		if (chrd.cpLim >= cp || chr.FBreak())
			{
			if (cp == cpLine)
				return chr.XpMin() + kdxpIndentTxtg;
			else
				return chrd.xpLim + kdxpIndentTxtg;
			}
		cpCur = chrd.cpLim;
		xpCur = chrd.xpLim;
		}
}


/***************************************************************************
	Replaces the characters between cp1 and cp2 with the given ones.
***************************************************************************/
bool TXTG::FReplace(achar *prgch, long cch, long cp1, long cp2)
{
	AssertThis(0);
	AssertIn(cch, 0, kcbMax);
	AssertPvCb(prgch, cch);
	AssertIn(cp1, 0, _ptxtb->CpMac());
	AssertIn(cp2, 0, _ptxtb->CpMac());

	HideSel();
	SortLw(&cp1, &cp2);
	if (!_ptxtb->FReplaceRgch(prgch, cch, cp1, cp2 - cp1))
		return fFalse;

	cp1 += cch;
	SetSel(cp1, cp1);
	ShowSel();
	return fTrue;
}


/***************************************************************************
	Invalidate the display from cp.  If we're the active TXTG, also redraw.
***************************************************************************/
void TXTG::InvalCp(long cp, long ccpIns, long ccpDel)
{
	AssertThis(0);
	AssertIn(cp, 0, _ptxtb->CpMac() + 1);
	AssertIn(ccpIns, 0, _ptxtb->CpMac() + 1 - cp);
	AssertIn(ccpDel, 0, kcbMax);
	Assert(!_fSelOn, "selection is on in InvalCp!");
	long cpAnchor, cpOther;

	//adjust the sel
	cpAnchor = _cpAnchor;
	cpOther = _cpOther;
	FAdjustIv(&cpAnchor, cp, ccpIns, ccpDel);
	FAdjustIv(&cpOther, cp, ccpIns, ccpDel);
	if (cpAnchor != _cpAnchor || cpOther != _cpOther)
		SetSel(cpAnchor, cpOther, ginNil);

	_ReformatAndDraw(cp, ccpIns, ccpDel);

	if (pvNil != _ptrul)
		{
		PAP pap;

		_FetchPap(LwMin(_cpAnchor, _cpOther), &pap);
		_ptrul->SetDxpTab(pap.dxpTab);
		_ptrul->SetDxpDoc(_DxpDoc());
		}
}


/***************************************************************************
	Reformat the TXTG and update the display.  If this TXTG is not the
	active one, the display is invalidated instead of updated.
***************************************************************************/
void TXTG::_ReformatAndDraw(long cp, long ccpIns, long ccpDel)
{
	RC rcLoc, rc;
	long yp, dypIns, dypDel;
	RC rcUpdate(0, 0, 0, 0);

	//reformat
	_Reformat(cp, ccpIns, ccpDel, &yp, &dypIns, &dypDel);

	//determine the dirty rectangles and if we're active, update them
	GetRc(&rcLoc, cooLocal);
	if (!_fActive)
		{
		rc = rcLoc;
		rc.ypTop = yp;
		if (dypIns == dypDel)
			rc.ypBottom = yp + dypIns;
		InvalRc(&rc);
		return;
		}

	rc = rcLoc;
	rc.ypTop = yp;
	rc.ypBottom = yp + dypIns;
	if (dypIns != dypDel)
		{
		// Have some bits to blt vertically. If the background isn't clear,
		// but _fMark is set, still do the scroll, since _fMark is intended
		// to avoid flashing (allowing offscreen drawing) and scrolling doesn't
		// flash anyway.
		if (_fClear)
			rc.ypBottom = rcLoc.ypBottom;
		else
			{
			rc = rcLoc;
			rc.ypTop = LwMax(rc.ypTop, yp + LwMin(dypIns, dypDel));
			Scroll(&rc, 0, dypIns - dypDel, _fMark ? kginMark : kginDraw);
			rc.ypBottom = rc.ypTop;
			rc.ypTop = yp;
			}
		}

	if (!rc.FEmpty())
		InvalRc(&rc, _fClear || _fMark ? kginMark : kginDraw);

	_fXpValid = fFalse;
}


/***************************************************************************
	Perform a scroll according to scaHorz and scaVert.
***************************************************************************/
void TXTG::_Scroll(long scaHorz, long scaVert, long scvHorz, long scvVert)
{
	RC rc;
	long dxp, dyp;

	GetRc(&rc, cooLocal);
	dxp = 0;
	switch (scaHorz)
		{
	case scaPageUp:
		dxp = -LwMulDiv(rc.Dxp(), 9, 10);
		goto LHorz;
	case scaPageDown:
		dxp = LwMulDiv(rc.Dxp(), 9, 10);
		goto LHorz;
	case scaLineUp:
		dxp = -rc.Dxp() / 10;
		goto LHorz;
	case scaLineDown:
		dxp = rc.Dxp() / 10;
		goto LHorz;
	case scaToVal:
		dxp = scvHorz - _scvHorz;
LHorz:
		dxp = LwBound(_scvHorz + dxp, 0, _ScvMax(fFalse) + 1) - _scvHorz;
		_scvHorz += dxp;

		if (pvNil != _ptrul)
			_ptrul->SetXpLeft(kdxpIndentTxtg - _scvHorz);
		break;
		}

	dyp = 0;
	if (scaVert != scaNil)
		{
		long cpT;
		LIN lin, linDisp;
		long ilin;
		RC rc;

		switch (scaVert)
			{
		case scaToVal:
			cpT = LwBound(scvVert, 0, _ptxtb->CpMac());
			_FindCp(cpT, &lin, &ilin);
			dyp = lin.dypTot - _dypDisp;
			_ilinDisp = ilin;
			_cpDisp = lin.cpMin;
			_dypDisp = lin.dypTot;
			break;

		case scaPageDown:
			// scroll down a page
			GetRc(&rc, cooLocal);
			_FindDyp(rc.Dyp() + _dypDisp, &lin, &ilin);

			if (lin.cpMin <= _cpDisp)
				{
				// we didn't go anywhere so force going down a line
				_FetchLin(_ilinDisp + 1, &lin, &ilin);
				}
			else if (lin.dypTot + lin.dyp > _dypDisp + rc.Dyp() &&
					ilin > _ilinDisp + 1)
				{
				// the line crosses the bottom of the ddg so back up one
				Assert(ilin > 0, 0);
				_FetchLin(ilin - 1, &lin, &ilin);
				}

			dyp = lin.dypTot - _dypDisp;
			_ilinDisp = ilin;
			_cpDisp = lin.cpMin;
			_dypDisp = lin.dypTot;
			break;

		case scaLineDown:
			// scroll down a line
			_FetchLin(_ilinDisp + 1, &lin, &_ilinDisp);
			dyp = lin.dypTot - _dypDisp;
			_cpDisp = lin.cpMin;
			_dypDisp = lin.dypTot;
			break;

		case scaPageUp:
			// scroll down a page
			if (_ilinDisp <= 0)
				break;
			GetRc(&rc, cooLocal);
			_FetchLin(_ilinDisp, &linDisp);
			Assert(linDisp.dypTot == _dypDisp, 0);

			// determine where to scroll to - try to keep the top line
			// visible, but scroll up at least one line
			dyp = LwMax(0, linDisp.dypTot + linDisp.dyp - rc.Dyp());
			_FindDyp(dyp, &lin, &ilin);
			if (lin.cpMin >= linDisp.cpMin)
				{
				// we didn't go anywhere so force going up a line
				_FetchLin(_ilinDisp - 1, &lin, &ilin);
				}
			else if (linDisp.dypTot + linDisp.dyp > lin.dypTot + rc.Dyp() &&
					ilin < _ilinDisp - 1)
				{
				// the previous disp line crosses the bottom of the ddg, so move
				// down one line
				_FetchLin(ilin + 1, &lin, &ilin);
				}

			dyp = lin.dypTot - _dypDisp;
			_ilinDisp = ilin;
			_cpDisp = lin.cpMin;
			_dypDisp = lin.dypTot;
			break;

		case scaLineUp:
			// scroll up a line
			if (_ilinDisp <= 0)
				break;
			_FetchLin(_ilinDisp - 1, &lin, &_ilinDisp);
			dyp = lin.dypTot - _dypDisp;
			_cpDisp = lin.cpMin;
			_dypDisp = lin.dypTot;
			break;
			}

		AssertIn(_cpDisp, 0, _ptxtb->CpMac());
		_scvVert = _cpDisp;
		}

	_SetScrollValues();
	if (dxp != 0 || dyp != 0)
		_ScrollDxpDyp(dxp, dyp);
}


/***************************************************************************
	Move the bits in the window.
***************************************************************************/
void TXTG::_ScrollDxpDyp(long dxp, long dyp)
{
	AssertThis(0);
	RC rcLoc, rcBad1, rcBad2;

	// determine the dirty rectangles and update them
	GetRc(&rcLoc, cooLocal);
	if (_fClear)
		{
		InvalRc(&rcLoc, kginMark);
		vpappb->UpdateMarked();
		return;
		}

	Scroll(&rcLoc, -dxp, -dyp, _fMark ? kginMark : kginDraw);
	if (_fMark)
		vpappb->UpdateMarked();
}


/***************************************************************************
	Update the display of the document.
***************************************************************************/
void TXTG::Draw(PGNV pgnv, RC *prcClip)
{
	AssertPo(pgnv, 0);
	AssertVarMem(prcClip);

	DrawLines(pgnv, prcClip, kdxpIndentTxtg - _scvHorz, 0, _ilinDisp);
	if (_fSelOn)
		_InvertSel(pgnv);
	_SetScrollValues();
}


/***************************************************************************
	Draws some lines of the document.
***************************************************************************/
void TXTG::DrawLines(PGNV pgnv, RC *prcClip, long dxp, long dyp,
	long ilinMin, long ilinLim, ulong grftxtg)
{
	AssertPo(pgnv, 0);
	AssertVarMem(prcClip);
	CHP chp;
	PAP pap;
	CHR chr;
	CHRD chrd;
	RC rc;
	long xpBase, xp, yp, xpChr, dxpDoc;
	long cpLine, cpCur, cpLimChp, cpLim, cpLimLine;
	LIN lin;
	long ilin;

	cpLim = _ptxtb->CpMac();
	dxpDoc = _DxpDoc();
	_FetchLin(ilinMin, &lin);
	cpLine = lin.cpMin;

	pgnv->FillRc(prcClip, _ptxtb->AcrBack());
	yp = dyp;
	for (ilin = ilinMin; ilin < ilinLim; ilin++)
		{
		if (yp >= prcClip->ypBottom || cpLine >= cpLim)
			break;

		_FetchLin(ilin, &lin);
		if (yp + lin.dyp <= prcClip->ypTop)
			{
			yp += lin.dyp;
			cpLine += lin.ccp;
			continue;
			}

		_FetchPap(cpLine, &pap);
		xpBase = lin.xpLeft + dxp;
		cpLimChp = cpCur = cpLine;
		cpLimLine = cpLine + lin.ccp;
		xpChr = 0;

		// draw the line
		for (;;)
			{
			// make sure the chp is valid
			if (cpCur >= cpLimChp)
				{
				_FetchChp(cpCur, &chp, pvNil, &cpLimChp);
				cpLimChp = LwMin(cpLimChp, cpLimLine);
				Assert(cpLimChp > cpCur, "why is cpCur >= cpLimChp?");
				chr.Init(&chp, &pap, _ptxtb, _pgnv, cpCur, cpLimChp,
					xpChr, dxpDoc, dxpDoc);
				}

			chr.GetNextRun(fTrue);
			chr.GetChrd(&chrd);

			if (chrd.cpLimDraw > cpCur)
				{
				// draw some text
				xp = xpBase + chr.XpMin();

				if (chr.FObject())
					{
					// draw the object
					_ptxtb->FDrawObject(chr.CpMin(), pgnv, &xp,
						yp + lin.dypAscent + chp.dypOffset, &chp, prcClip);
					}
				else
					{
					pgnv->SetFont(chp.onn, chp.grfont, chp.dypFont,
						tahLeft, tavBaseline);
	
					pgnv->DrawRgch(chr.Prgch(), chrd.cpLimDraw - chr.CpMin(),
						xp, yp + lin.dypAscent + chp.dypOffset,
						chp.acrFore, chp.acrBack);
					}
				}

			if (chrd.cpLim >= cpLimLine || chr.FBreak())
				break;

			cpCur = chrd.cpLim;
			xpChr = chrd.xpLim;
			}

		_DrawLinExtra(pgnv, prcClip, &lin, dxp, yp, grftxtg);

		yp += lin.dyp;
		cpLine = cpLimLine;
		}
}


/***************************************************************************
	Gives a subclass an opportunity to draw extra stuff associated with
	the line. Default does nothing.
***************************************************************************/
void TXTG::_DrawLinExtra(PGNV pgnv, PRC prcClip, LIN *plin,
	long dxp, long yp, ulong grftxtg)
{
}


/***************************************************************************
	Handle a mousedown in the TXTG.
***************************************************************************/
bool TXTG::FCmdTrackMouse(PCMD_MOUSE pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);
	RC rc;
	long cp;
	long scaHorz, scaVert;
	long xp = pcmd->xp;
	long yp = pcmd->yp;

	if (pcmd->cid == cidMouseDown)
		{
		Assert(vpcex->PgobTracking() == pvNil, "mouse already being tracked!");
		_fSelByWord = (pcmd->cact > 1) && !(pcmd->grfcust & fcustShift);
		vpcex->TrackMouse(this);
		}
	else
		{
		Assert(vpcex->PgobTracking() == this, "not tracking mouse!");
		Assert(pcmd->cid == cidTrackMouse, 0);
		}

	//do autoscrolling
	GetRc(&rc, cooLocal);
	if (!FIn(xp, rc.xpLeft, rc.xpRight))
		{
		scaHorz = (xp < rc.xpLeft) ? scaLineUp : scaLineDown;
		xp = LwBound(xp, rc.xpLeft, rc.xpRight);
		}
	else
		scaHorz = scaNil;
	if (!FIn(yp, rc.ypTop, rc.ypBottom))
		{
		scaVert = (yp < rc.ypTop) ? scaLineUp : scaLineDown;
		yp = LwBound(yp, rc.ypTop, rc.ypBottom);
		}
	else
		scaVert = scaNil;
	if (scaHorz != scaNil || scaVert != scaNil)
		_Scroll(scaHorz, scaVert);

	//set the selection
	if (_FGetCpFromPt(xp, yp, &cp, !_fSelByWord))
		{
		if (pcmd->cid != cidMouseDown || (pcmd->grfcust & fcustShift))
			{
			if (_fSelByWord)
				{
				cp = (cp < _cpAnchor) ? _ptxtb->CpPrev(cp + 1, fTrue) :
					_ptxtb->CpNext(cp, fTrue);
				}
			SetSel(_cpAnchor, cp);
			}
		else
			{
			if (_fSelByWord)
				cp = _ptxtb->CpPrev(cp + 1, fTrue);
			SetSel(cp, _fSelByWord ? _ptxtb->CpNext(cp, fTrue) : cp);
			}
		_fXpValid = fFalse;
		}
	_SwitchSel(fTrue);	//make sure the selection is on

	if (!(pcmd->grfcust & fcustMouse))
		vpcex->EndMouseTracking();

	return fTrue;
}


/***************************************************************************
	Do idle processing.  If this handler has the active selection, make sure
	the selection is on or off according to rglw[0] (non-zero means on)
	and set rglw[0] to false.  Always return false.
***************************************************************************/
bool TXTG::FCmdSelIdle(PCMD pcmd)
{
	AssertThis(0);

	// if rglw[1] is this one's hid, don't change the sel state.
	if (pcmd->rglw[1] != Hid())
		{
		if (!pcmd->rglw[0])
			_SwitchSel(fFalse, kginDefault);
		else if (_cpAnchor != _cpOther || _tsSel == 0)
			_SwitchSel(fTrue);
		else if (DtsCaret() < TsCurrent() - _tsSel)
			_SwitchSel(!_fSelOn);
		}
	pcmd->rglw[0] = fFalse;
	return fFalse;
}


/***************************************************************************
	Get the current selection.
***************************************************************************/
void TXTG::GetSel(long *pcpAnchor, long *pcpOther)
{
	AssertThis(0);
	AssertVarMem(pcpAnchor);
	AssertVarMem(pcpOther);

	*pcpAnchor = _cpAnchor;
	*pcpOther = _cpOther;
}


/***************************************************************************
	Set the selection.
***************************************************************************/
void TXTG::SetSel(long cpAnchor, long cpOther, long gin)
{
	AssertThis(0);
	long cpMac = _ptxtb->CpMac();

	cpAnchor = LwBound(cpAnchor, 0, cpMac);
	cpOther = LwBound(cpOther, 0, cpMac);

	if (cpAnchor == _cpAnchor && cpOther == _cpOther)
		return;

	if (_fSelOn)
		{
		if ((_fMark || _fClear) && gin == kginDraw)
			gin = kginMark;
		_pgnv->SetGobRc(this);
		if (_cpAnchor != cpAnchor || _cpAnchor == _cpOther ||
				cpAnchor == cpOther)
			{
			_InvertSel(_pgnv, gin);
			_cpAnchor = cpAnchor;
			_cpOther = cpOther;
			_InvertSel(_pgnv, gin);
			_tsSel = TsCurrent();
			}
		else
			{
			//they have the same anchor and neither is an insertion
			_InvertCpRange(_pgnv, _cpOther, cpOther, gin);
			_cpOther = cpOther;
			}
		}
	else
		{
		_cpAnchor = cpAnchor;
		_cpOther = cpOther;
		_tsSel = 0L;
		}
}


/***************************************************************************
	Make sure the selection is visible (at least the _cpOther end of it).
***************************************************************************/
void TXTG::ShowSel(void)
{
	AssertThis(0);
	long cpScroll;
	long ilinOther, ilinAnchor, ilin;
	long dxpScroll, dypSel;
	long xpMin, xpLim;
	RC rc;
	LIN linOther, linAnchor, lin;
	long cpAnchor = _cpAnchor;

	//find the lines we want to show
	_FindCp(_cpOther, &linOther, &ilinOther);
	_FindCp(_cpAnchor, &linAnchor, &ilinAnchor);
	linOther.dypTot -= _dypDisp;
	linAnchor.dypTot -= _dypDisp;
	GetRc(&rc, cooLocal);

	cpScroll = _cpDisp;
	if (!FIn(linOther.dypTot, 0, rc.Dyp() - linOther.dyp) ||
			!FIn(linAnchor.dypTot, 0, rc.Dyp() - linAnchor.dyp))
		{
		if (_cpOther < cpAnchor)
			dypSel = linAnchor.dypTot - linOther.dypTot + linAnchor.dyp;
		else
			dypSel = linOther.dypTot - linAnchor.dypTot + linOther.dyp;
		if (dypSel > rc.Dyp())
			{
			//just show _cpOther
            cpAnchor = _cpOther;
			ilinAnchor = ilinOther;
			linAnchor = linOther;
			dypSel = linOther.dyp;
			}

		if (linOther.dypTot < 0 || linAnchor.dypTot < 0)
			{
			//scroll up
			cpScroll = LwMin(linOther.cpMin, linAnchor.cpMin);
			}
		else if (linOther.dypTot + linOther.dyp >= rc.Dyp() ||
				linAnchor.dypTot + linAnchor.dyp >= rc.Dyp())
			{
			//scroll down
			ilin = LwMin(ilinOther, ilinAnchor);
			cpScroll = LwMin(linOther.cpMin, linAnchor.cpMin);
			dypSel = rc.Dyp() - dypSel;
			for (;;)
				{
				if (--ilin < 0)
					break;
				_FetchLin(ilin, &lin);
				if (lin.dyp > dypSel)
					break;
				cpScroll -= lin.ccp;
				dypSel -= lin.dyp;
				}
			}
		}

	//now do the horizontal stuff
	xpMin = _DxpFromCp(linOther.cpMin, _cpOther) + linOther.xpLeft - _scvHorz;
	xpLim = _DxpFromCp(linAnchor.cpMin, cpAnchor) + linAnchor.xpLeft - _scvHorz;
	if (LwAbs(xpLim - xpMin) > rc.Dxp())
		{
		//can't show both
		if (xpMin > xpLim)
			{
			xpLim = xpMin;
			xpMin = xpLim - rc.Dxp();
			}
		else
			xpLim = xpMin + rc.Dxp();
		}
	else
		SortLw(&xpMin, &xpLim);
	dxpScroll = LwMax(LwMin(0, rc.xpRight - xpLim), rc.xpLeft - xpMin);

	if (dxpScroll != 0 || cpScroll != _cpDisp)
		_Scroll(scaToVal, scaToVal, _scvHorz - dxpScroll, cpScroll);
}


/***************************************************************************
	Turn the selection off.
***************************************************************************/
void TXTG::HideSel(void)
{
	AssertThis(0);

	if (!_fSelOn)
		return;

	_SwitchSel(fFalse);
	_tsSel = 0;
}


/***************************************************************************
	Turn the sel on or off according to fOn.
***************************************************************************/
void TXTG::_SwitchSel(bool fOn, long gin)
{
	AssertThis(0);

	if (FPure(fOn) != FPure(_fSelOn))
		{
		if ((_fMark || _fClear) && gin == kginDraw)
			gin = kginMark;
		_pgnv->SetGobRc(this);
		_InvertSel(_pgnv, gin);
		_fSelOn = FPure(fOn);
		_tsSel = TsCurrent();
		}
}


/***************************************************************************
	Invert the current selection.
***************************************************************************/
void TXTG::_InvertSel(PGNV pgnv, long gin)
{
	AssertThis(0);
	AssertPo(pgnv, 0);
	RC rc, rcT;

	GetRc(&rc, cooLocal);
	if (_cpAnchor == _cpOther)
		{
		//insertion bar
		_GetXpYpFromCp(_cpAnchor, &rcT.ypTop, &rcT.ypBottom, &rcT.xpLeft);
		rcT.xpRight = --rcT.xpLeft + 2;
		if (rcT.FIntersect(&rc))
			{
			if (kginDraw == gin)
				pgnv->FillRc(&rcT, kacrInvert);
			else
				InvalRc(&rcT, gin);
			}
		}
	else
		_InvertCpRange(pgnv, _cpAnchor, _cpOther, gin);
}


/***************************************************************************
	Invert a range.
***************************************************************************/
void TXTG::_InvertCpRange(PGNV pgnv, long cp1, long cp2, long gin)
{
	AssertThis(0);
	AssertPo(pgnv, 0);
	AssertIn(cp1, 0, _ptxtb->CpMac());
	AssertIn(cp2, 0, _ptxtb->CpMac());
	RC rc1, rc2, rcClip, rcT, rcDoc;

	if (cp1 == cp2)
		return;
	SortLw(&cp1, &cp2);
	_GetXpYpFromCp(cp1, &rc1.ypTop, &rc1.ypBottom, &rc1.xpLeft);
	_GetXpYpFromCp(cp2, &rc2.ypTop, &rc2.ypBottom, &rc2.xpRight);
	GetRc(&rcClip, cooLocal);
	if (rc1.ypTop >= rcClip.ypBottom || rc2.ypBottom <= rcClip.ypTop)
		return;

	if (rc1.ypTop == rc2.ypTop && rc1.ypBottom == rc2.ypBottom)
		{
		// only one line involved
		rc1.xpRight = rc2.xpRight;
		if (rcT.FIntersect(&rc1, &rcClip))
			{
			if (kginDraw == gin)
				pgnv->HiliteRc(&rcT, kacrWhite);
			else
				InvalRc(&rcT, gin);
			}
		return;
		}

	// invert the sel on the first line
	rc1.xpRight = kdxpIndentTxtg - _scvHorz + _DxpDoc();
	if (rcT.FIntersect(&rc1, &rcClip))
		{
		if (kginDraw == gin)
			pgnv->HiliteRc(&rcT, kacrWhite);
		else
			InvalRc(&rcT, gin);
		}

	//invert the main rectangular block
	rc1.xpLeft = kdxpIndentTxtg - _scvHorz;
	rc1.ypTop = rc1.ypBottom;
	rc1.ypBottom = rc2.ypTop;
	if (rcT.FIntersect(&rc1, &rcClip))
		{
		if (kginDraw == gin)
			pgnv->HiliteRc(&rcT, kacrWhite);
		else
			InvalRc(&rcT, gin);
		}

	//invert the last line
	rc2.xpLeft = rc1.xpLeft;
	if (rcT.FIntersect(&rc2, &rcClip))
		{
		if (kginDraw == gin)
			pgnv->HiliteRc(&rcT, kacrWhite);
		else
			InvalRc(&rcT, gin);
		}
}


/***************************************************************************
	Handle a key down.
***************************************************************************/
bool TXTG::FCmdKey(PCMD_KEY pcmd)
{
	const long kcchInsBuf = 64;
	AssertThis(0);
	AssertVarMem(pcmd);
	ulong grfcust;
	long vkDone;
	long ichLim;
	long cact;
	long dcp, cpT;
	CMD cmd;
	LIN lin, linT;
	long ilin, ilinT;
	RC rc;
	achar rgch[kcchInsBuf + 1];

	// keep fetching characters until we get a cursor key, delete key or
	// until the buffer is full.
	vkDone = vkNil;
	ichLim = 0;
	do
		{
		grfcust = pcmd->grfcust;
		switch (pcmd->vk)
			{
		//these keys all terminate the key fetching loop
		case kvkHome:
		case kvkEnd:
		case kvkLeft:
		case kvkRight:
		case kvkUp:
		case kvkDown:
		case kvkPageUp:
		case kvkPageDown:
		case kvkDelete:
		case kvkBack:
			vkDone = pcmd->vk;
			goto LInsert;

		default:
			if (chNil == pcmd->ch)
				break;
			for (cact = 0; cact < pcmd->cact && ichLim < kcchInsBuf; cact++)
				{
				rgch[ichLim++] = (achar)pcmd->ch;
#ifdef WIN
				if ((achar)pcmd->ch == kchReturn)
					rgch[ichLim++] = kchLineFeed;
#endif //WIN
				}
			break;
			}

		pcmd = (PCMD_KEY)&cmd;
		}
	while (ichLim < kcchInsBuf && vpcex->FGetNextKey(&cmd));

LInsert:
	if (ichLim > 0)
		{
		//have some characters to insert
		FReplace(rgch, ichLim, _cpAnchor, _cpOther);
		}

	dcp = 0;
	switch (vkDone)
		{
	case kvkHome:
		if (grfcust & fcustCmd)
			dcp = -_cpOther;
		else
			{
			_FindCp(_cpOther, &lin);
			dcp = lin.cpMin - _cpOther;
			}
		_fXpValid = fFalse;
		goto LSetSel;

	case kvkEnd:
		if (grfcust & fcustCmd)
			dcp = _ptxtb->CpMac() - _cpOther;
		else
			{
			_FindCp(_cpOther, &lin);
			cpT = _ptxtb->CpPrev(lin.cpMin + lin.ccp);
			AssertIn(cpT, _cpOther, _ptxtb->CpMac());
			dcp = cpT - _cpOther;
			}
		_fXpValid = fFalse;
		goto LSetSel;

	case kvkLeft:
		dcp = _ptxtb->CpPrev(_cpOther, FPure(grfcust & fcustCmd)) - _cpOther;
		_fXpValid = fFalse;
		goto LSetSel;

	case kvkRight:
		dcp = _ptxtb->CpNext(_cpOther, FPure(grfcust & fcustCmd)) - _cpOther;
		_fXpValid = fFalse;
		goto LSetSel;

	case kvkUp:
	case kvkPageUp:
	case kvkDown:
	case kvkPageDown:
		// get the LIN for _cpOther and make sure _xpSel is up to date
		_FindCp(_cpOther, &lin, &ilin);
		if (!_fXpValid)
			{
			// get the xp of _cpOther
			_xpSel = lin.xpLeft + _DxpFromCp(lin.cpMin, _cpOther);
			_fXpValid = fTrue;
			}

		switch (vkDone)
			{
		case kvkUp:
			if (ilin == 0)
				{
				dcp = -_cpOther;
				goto LSetSel;
				}
			_FetchLin(ilin - 1, &lin);
			break;

		case kvkPageUp:
			if (ilin == 0)
				{
				dcp = -_cpOther;
				goto LSetSel;
				}
			GetRc(&rc, cooLocal);
			_FindDyp(LwMax(0, lin.dypTot - rc.Dyp() + lin.dyp), &linT, &ilinT);

			if (linT.cpMin >= lin.cpMin)
				{
				// we didn't go anywhere so force going up a line
				_FetchLin(ilin - 1, &lin);
				}
			else if (lin.dypTot + lin.dyp > linT.dypTot + rc.Dyp() &&
					ilinT < ilin - 1)
				{
				// the previous line crosses the bottom of the ddg, so move
				// down one line
				_FetchLin(ilinT + 1, &lin);
				}
			else
				lin = linT;
			break;

		case kvkDown:
			if (lin.cpMin + lin.ccp >= _ptxtb->CpMac())
				{
				dcp = _ptxtb->CpMac() - _cpOther;
				goto LSetSel;
				}
			_FetchLin(ilin + 1, &lin);
			break;

		case kvkPageDown:
			if (lin.cpMin + lin.ccp >= _ptxtb->CpMac())
				{
				dcp = _ptxtb->CpMac() - _cpOther;
				goto LSetSel;
				}

			GetRc(&rc, cooLocal);
			_FindDyp(lin.dypTot + rc.Dyp(), &linT, &ilinT);

			if (linT.cpMin <= lin.cpMin)
				{
				// we didn't go anywhere so force going down a line
				_FetchLin(ilin + 1, &lin);
				}
			else if (linT.dypTot + linT.dyp > lin.dypTot + rc.Dyp()
					&& ilinT > ilin + 1)
				{
				// the line crosses the bottom of the ddg so back up one line
				Assert(ilinT > 0, 0);
				_FetchLin(ilinT - 1, &lin);
				}
			else
				lin = linT;
			break;
			}

		// we have the line, now find the position on the line
		_FGetCpFromXp(_xpSel - _scvHorz, &lin, &dcp);
		dcp -= _cpOther;

LSetSel:
		//move the selection
		if (grfcust & fcustShift)
			{
			//extend selection
			SetSel(_cpAnchor, _cpOther + dcp);
			ShowSel();
			}
		else
			{
			cpT = _cpOther;
			if (cpT == _cpAnchor || (grfcust & fcustCmd))
				cpT += dcp;
			else if ((dcp > 0) != (cpT > _cpAnchor))
				cpT = _cpAnchor;
			SetSel(cpT, cpT);
			ShowSel();
			}
		break;

	case kvkDelete:
	case kvkBack:
		if (_cpAnchor != _cpOther)
			dcp = _cpOther - _cpAnchor;
		else if (vkDone == kvkDelete)
			{
			dcp = _ptxtb->CpNext(_cpAnchor) - _cpAnchor;
			if (_cpAnchor + dcp >= _ptxtb->CpMac())
				dcp = 0;
			}
		else
			dcp = _ptxtb->CpPrev(_cpAnchor) - _cpAnchor;
		if (dcp != 0)
			FReplace(pvNil, 0, _cpAnchor, _cpAnchor + dcp);
		break;
		}

	return fTrue;
}


/***************************************************************************
	Return the maximum scroll value for this view of the doc.
***************************************************************************/
long TXTG::_ScvMax(bool fVert)
{
	RC rc;
	long dxp;

	if (fVert)
		return _ptxtb->CpMac() - 1;

	dxp = _DxpDoc() + 2 * kdxpIndentTxtg;
	GetRc(&rc, cooLocal);
	return LwMax(0, dxp - rc.Dxp());
}


/***************************************************************************
	Return the logical width of the text "page".
***************************************************************************/
long TXTG::_DxpDoc(void)
{
	return _ptxtb->DxpDef();
}


/***************************************************************************
	Set the tab width.  Default does nothing.
***************************************************************************/
void TXTG::SetDxpTab(long dxp)
{
	AssertThis(0);
}


/***************************************************************************
	Set the document width.  Default calls SetDxpDef on the TXTB.
***************************************************************************/
void TXTG::SetDxpDoc(long dxp)
{
	AssertThis(0);
	dxp = LwBound(dxp, 1, kcbMax);
	_ptxtb->SetDxpDef(dxp);
}


/***************************************************************************
	Show or hide the ruler.
***************************************************************************/
void TXTG::ShowRuler(bool fShow)
{
	AssertThis(0);
	RC rcAbs, rcRel;
	long dyp;

	if (FPure(fShow) == (_ptrul != pvNil))
		return;

	dyp = _DypTrul();
	if (fShow)
		{
		PGOB pgob;
		GCB gcb;

		pgob = PgobPar();
		if (pvNil == pgob || !pgob->FIs(kclsDSG))
			return;

		GetPos(&rcAbs, &rcRel);
		rcAbs.ypTop += dyp;
		SetPos(&rcAbs, &rcRel);

		rcRel.ypBottom = rcRel.ypTop;
		rcAbs.ypBottom = rcAbs.ypTop;
		rcAbs.ypTop -= dyp;
		gcb.Set(HidUnique(), pgob, fgobNil, kginDefault, &rcAbs, &rcRel);

		if (pvNil == (_ptrul = _PtrulNew(&gcb)))
			goto LFail;
		}
	else
		{
		ReleasePpo(&_ptrul);
LFail:
		GetPos(&rcAbs, &rcRel);
		rcAbs.ypTop -= dyp;
		SetPos(&rcAbs, &rcRel);
		}
}


/***************************************************************************
	Return the height of the ruler.
***************************************************************************/
long TXTG::_DypTrul(void)
{
	AssertThis(0);
	return 0;
}


/***************************************************************************
	Create the ruler.
***************************************************************************/
PTRUL TXTG::_PtrulNew(PGCB pgcb)
{
	AssertThis(0);
	return pvNil;
}


/***************************************************************************
	Get the natural width and height of the view on the document.
***************************************************************************/
void TXTG::GetNaturalSize(long *pdxp, long *pdyp)
{
	AssertThis(0);
	AssertNilOrVarMem(pdxp);
	AssertNilOrVarMem(pdyp);
	LIN lin;

	if (pvNil != pdxp)
		*pdxp = _DxpDoc() + 2 * kdxpIndentTxtg;
	if (pvNil == pdyp)
		return;

	_FetchLin(kcbMax - 1, &lin);
	*pdyp = lin.dypTot + lin.dyp;
}


/***************************************************************************
	Constructor for the plain line text document display gob.
***************************************************************************/
TXLG::TXLG(PTXTB ptxtb, PGCB pgcb, long onn, ulong grfont, long dypFont, long cchTab)
	: TXLG_PAR(ptxtb, pgcb)
{
	RC rc;
	achar ch = kchSpace;
	GNV gnv(this);

	gnv.SetFont(onn, fontNil, dypFont);
	gnv.GetRcFromRgch(&rc, &ch, 1);
	_dxpChar = rc.Dxp();
	_onn = onn;
	_grfont = grfont;
	_dypFont = dypFont;
	_cchTab = LwBound(cchTab, 1, kcbMax);
}


/***************************************************************************
	Static method to create a new plain line text doc display gob.
***************************************************************************/
PTXLG TXLG::PtxlgNew(PTXTB ptxtb, PGCB pgcb, long onn, ulong grfont,
	long dypFont, long cchTab)
{
	PTXLG ptxlg;

	if (pvNil == (ptxlg = NewObj TXLG(ptxtb, pgcb, onn, grfont, dypFont, cchTab)))
		return pvNil;
	if (!ptxlg->_FInit())
		{
		ReleasePpo(&ptxlg);
		return pvNil;
		}
	ptxlg->Activate(fTrue);
	return ptxlg;
}


/***************************************************************************
	Get the width of the logical "page".  For a TXLG, this is some big
	value, so we do no word wrap.
***************************************************************************/
long TXLG::_DxpDoc(void)
{
	return kswMax;
}


/***************************************************************************
	Set the tab width.
***************************************************************************/
void TXLG::SetDxpTab(long dxp)
{
	AssertThis(0);
	long cch;

	cch = LwBound(dxp / _dxpChar, 1, kswMax / _dxpChar);
	if (cch != _cchTab)
		{
		_cchTab = cch;
		InvalCp(0, _ptxtb->CpMac(), _ptxtb->CpMac());
		}
}


/***************************************************************************
	Set the document width.  Does nothing.
***************************************************************************/
void TXLG::SetDxpDoc(long dxp)
{
	AssertThis(0);
}


/***************************************************************************
	Get the character properties for display.  These are the same for all
	characters in the TXLG.
***************************************************************************/
void TXLG::_FetchChp(long cp, PCHP pchp, long *pcpMin, long *pcpLim)
{
	AssertIn(cp, 0, _ptxtb->CpMac());
	AssertVarMem(pchp);
	AssertNilOrVarMem(pcpMin);
	AssertNilOrVarMem(pcpLim);

	pchp->Clear();
	pchp->grfont = _grfont;
	pchp->onn = _onn;
	pchp->dypFont = _dypFont;
	pchp->acrFore = kacrBlack;
	pchp->acrBack = kacrWhite;
	if (pvNil != pcpMin)
		*pcpMin = 0;
	if (pvNil != pcpLim)
		*pcpLim = _ptxtb->CpMac();
}


/***************************************************************************
	Get the paragraph properties for disply.  These are constant for all
	characters in the TXLG.
***************************************************************************/
void TXLG::_FetchPap(long cp, PPAP ppap, long *pcpMin, long *pcpLim)
{
	AssertIn(cp, 0, _ptxtb->CpMac());
	AssertVarMem(ppap);
	AssertNilOrVarMem(pcpMin);
	AssertNilOrVarMem(pcpLim);

	ClearPb(ppap, size(PAP));
	ppap->dxpTab = (short)LwMul(_cchTab, _dxpChar);
	ppap->numLine = kdenLine;
	ppap->numAfter = kdenAfter;
	if (pvNil != pcpMin)
		*pcpMin = 0;
	if (pvNil != pcpLim)
		*pcpLim = _ptxtb->CpMac();
}


/***************************************************************************
	Copy the selection.
***************************************************************************/
bool TXLG::_FCopySel(PDOCB *ppdocb)
{
	AssertThis(0);
	AssertNilOrVarMem(ppdocb);
	PTXPD ptxpd;

	if (_cpAnchor == _cpOther)
		return fFalse;

	if (pvNil == ppdocb)
		return fTrue;

	if (pvNil != (ptxpd = TXPD::PtxpdNew(pvNil)))
		{
		long cpMin = LwMin(_cpAnchor, _cpOther);
		long cpLim = LwMax(_cpAnchor, _cpOther);

		ptxpd->SuspendUndo();
		if (!ptxpd->FReplaceBsf(_ptxtb->Pbsf(), cpMin, cpLim - cpMin,
				0, 0, fdocNil))
			{
			ReleasePpo(&ptxpd);
			}
		else
			ptxpd->ResumeUndo();
		}

	*ppdocb = ptxpd;
	return pvNil != *ppdocb;
}


/***************************************************************************
	Delete the selection.
***************************************************************************/
void TXLG::_ClearSel(void)
{
	AssertThis(0);

	FReplace(pvNil, 0, _cpAnchor, _cpOther);
	ShowSel();
}


/***************************************************************************
	Paste the selection.
***************************************************************************/
bool TXLG::_FPaste(PCLIP pclip, bool fDoIt, long cid)
{
	AssertThis(0);
	AssertPo(pclip, 0);
	long cp1, cp2;
	long ccp;
	PTXTB ptxtb;

	if (cid != cidPaste || !pclip->FGetFormat(kclsTXTB))
		return fFalse;

	if (!fDoIt)
		return fTrue;

	if (!pclip->FGetFormat(kclsTXTB, (PDOCB *)&ptxtb))
		return fFalse;

	AssertPo(ptxtb, 0);
	if ((ccp = ptxtb->CpMac() - 1) <= 0)
		{
		ReleasePpo(&ptxtb);
		return fTrue;
		}

	HideSel();
	cp1 = LwMin(_cpAnchor, _cpOther);
	cp2 = LwMax(_cpAnchor, _cpOther);
	if (!_ptxtb->FReplaceTxtb(ptxtb, 0, ccp, cp1, cp2 - cp1))
		{
		ReleasePpo(&ptxtb);
		return fFalse;
		}
	ReleasePpo(&ptxtb);

	cp1 += ccp;
	SetSel(cp1, cp1);
	ShowSel();
	return fTrue;
}


/***************************************************************************
	Constructor for a rich text document display gob.
***************************************************************************/
TXRG::TXRG(PTXRD ptxrd, PGCB pgcb) : TXRG_PAR(ptxrd, pgcb)
{
}


/***************************************************************************
	Create a new rich text document display GOB.
***************************************************************************/
PTXRG TXRG::PtxrgNew(PTXRD ptxrd, PGCB pgcb)
{
	PTXRG ptxrg;

	if (pvNil == (ptxrg = NewObj TXRG(ptxrd, pgcb)))
		return pvNil;
	if (!ptxrg->_FInit())
		{
		ReleasePpo(&ptxrg);
		return pvNil;
		}
	ptxrg->Activate(fTrue);
	return ptxrg;
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a TXRG.
***************************************************************************/
void TXRG::AssertValid(ulong grf)
{
	TXRG_PAR::AssertValid(0);
	AssertNilOrPo(_ptrul, 0);
}
#endif //DEBUG


/***************************************************************************
	Get the character properties for displaying the given cp.
***************************************************************************/
void TXRG::_FetchChp(long cp, PCHP pchp, long *pcpMin, long *pcpLim)
{
	((PTXRD)_ptxtb)->FetchChp(cp, pchp, pcpMin, pcpLim);
}


/***************************************************************************
	Get the paragraph properties for displaying the given cp.
***************************************************************************/
void TXRG::_FetchPap(long cp, PPAP ppap, long *pcpMin, long *pcpLim)
{
	((PTXRD)_ptxtb)->FetchPap(cp, ppap, pcpMin, pcpLim);
}


/***************************************************************************
	Set the tab width for the currently selected paragraph(s).
***************************************************************************/
void TXRG::SetDxpTab(long dxp)
{
	AssertThis(0);
	long cpMin, cpLim, cpAnchor, cpOther;
	PAP papOld, papNew;

	dxp = LwRoundClosest(dxp, kdzpInch / 8);
	dxp = LwBound(dxp, kdzpInch / 8, 100 * kdzpInch);
	ClearPb(&papOld, size(PAP));
	papNew = papOld;

	cpMin = LwMin(cpAnchor = _cpAnchor, cpOther = _cpOther);
	cpLim = LwMax(_cpAnchor, _cpOther);

	papNew.dxpTab = (short)dxp;
	_SwitchSel(fFalse, ginNil);
	if (!((PTXRD)_ptxtb)->FApplyPap(cpMin, cpLim - cpMin, &papNew, &papOld,
			&cpMin, &cpLim))
		{
		_SwitchSel(fTrue, kginMark);
		}

	SetSel(cpAnchor, cpOther);
	ShowSel();
}


/***************************************************************************
	Set the selection for the TXRG.  Invalidates _chpIns if the selection
	changes.
***************************************************************************/
void TXRG::SetSel(long cpAnchor, long cpOther, long gin)
{
	AssertThis(0);
	long cpMac = _ptxtb->CpMac();

	cpAnchor = LwBound(cpAnchor, 0, cpMac);
	cpOther = LwBound(cpOther, 0, cpMac);

	if (cpAnchor == _cpAnchor && cpOther == _cpOther)
		return;

	_fValidChp = fFalse;
	TXRG_PAR::SetSel(cpAnchor, cpOther, gin);
	if (pvNil != _ptrul)
		{
		PAP pap;

		_FetchPap(LwMin(_cpAnchor, _cpOther), &pap);
		_ptrul->SetDxpTab(pap.dxpTab);
		}
}


/***************************************************************************
	Get the chp to use to replace the given range of characters. If the
	range is empty and not at the beginning of a paragraph, gets the chp
	of the previous character. Otherwise gets the chp of the character at
	LwMin(cp1, cp2).
***************************************************************************/
void TXRG::_FetchChpSel(long cp1, long cp2, PCHP pchp)
{
	AssertThis(0);
	AssertVarMem(pchp);

	long cp = LwMin(cp1, cp2);

	if (cp1 == cp2 && cp1 > 0)
		{
		if (!_ptxtb->FMinPara(cp) ||
				_ptxtb->FMinPara(cp2 = _ptxtb->CpNext(cp)) ||
				cp2 >= _ptxtb->CpMac())
			{
			cp = _ptxtb->CpPrev(cp);
			}
		}

	// NOTE: don't just call _FetchChp here.  _FetchChp allows derived
	// classes to modify the chp used for display without affecting the
	// chp used for editing.  This chp is an editing chp.  Same note
	// applies several other places in this file.
	((PTXRD)_ptxtb)->FetchChp(cp, pchp);
}


/***************************************************************************
	Make sure the _chpIns is valid.
***************************************************************************/
void TXRG::_EnsureChpIns(void)
{
	AssertThis(0);

	if (!_fValidChp)
		{
		_FetchChpSel(_cpAnchor, _cpOther, &_chpIns);
		_fValidChp = fTrue;
		}
}


/***************************************************************************
	Replaces the characters between cp1 and cp2 with the given ones.
***************************************************************************/
bool TXRG::FReplace(achar *prgch, long cch, long cp1, long cp2)
{
	AssertIn(cch, 0, kcbMax);
	AssertPvCb(prgch, cch);
	AssertIn(cp1, 0, _ptxtb->CpMac());
	AssertIn(cp2, 0, _ptxtb->CpMac());
	CHP chp;
	bool fSetChpIns = fFalse;

	HideSel();
	SortLw(&cp1, &cp2);
	if (cp1 == LwMin(_cpAnchor, _cpOther) && cp2 == LwMax(_cpAnchor, _cpOther))
		{
		_EnsureChpIns();
		chp = _chpIns;
		fSetChpIns = fTrue;
		}
	else
		_FetchChpSel(cp1, cp2, &chp);

	if (!((PTXRD)_ptxtb)->FReplaceRgch(prgch, cch, cp1, cp2 - cp1, &chp))
		return fFalse;

	cp1 += cch;
	SetSel(cp1, cp1);
	ShowSel();

	if (fSetChpIns)
		{
		// preserve the _chpIns
		_chpIns = chp;
		_fValidChp = fTrue;
		}

	return fTrue;
}


/***************************************************************************
	Copy the selection.
***************************************************************************/
bool TXRG::_FCopySel(PDOCB *ppdocb)
{
	AssertNilOrVarMem(ppdocb);
	PTXRD ptxrd;

	if (_cpAnchor == _cpOther)
		return fFalse;

	if (pvNil == ppdocb)
		return fTrue;

	if (pvNil != (ptxrd = TXRD::PtxrdNew(pvNil)))
		{
		long cpMin = LwMin(_cpAnchor, _cpOther);
		long cpLim = LwMax(_cpAnchor, _cpOther);

		ptxrd->SuspendUndo();
		if (!ptxrd->FReplaceTxrd((PTXRD)_ptxtb, cpMin, cpLim - cpMin,
				0, 0, fdocNil))
			{
			ReleasePpo(&ptxrd);
			}
		else
			ptxrd->ResumeUndo();
		}

	*ppdocb = ptxrd;
	return pvNil != *ppdocb;
}


/***************************************************************************
	Delete the selection.
***************************************************************************/
void TXRG::_ClearSel(void)
{
	FReplace(pvNil, 0, _cpAnchor, _cpOther);
	ShowSel();
}


/***************************************************************************
	Paste the selection.
***************************************************************************/
bool TXRG::_FPaste(PCLIP pclip, bool fDoIt, long cid)
{
	AssertThis(0);
	long cp1, cp2;
	long ccp;
	PTXTB ptxtb;
	bool fRet;

	if (cid != cidPaste || !pclip->FGetFormat(kclsTXTB))
		return fFalse;

	if (!fDoIt)
		return fTrue;

	if (!pclip->FGetFormat(kclsTXRD, (PDOCB *)&ptxtb) &&
			!pclip->FGetFormat(kclsTXTB, (PDOCB *)&ptxtb))
		{
		return fFalse;
		}

	AssertPo(ptxtb, 0);
	if ((ccp = ptxtb->CpMac() - 1) <= 0)
		{
		ReleasePpo(&ptxtb);
		return fTrue;
		}

	HideSel();
	cp1 = LwMin(_cpAnchor, _cpOther);
	cp2 = LwMax(_cpAnchor, _cpOther);
	_ptxtb->BumpCombineUndo();
	if (ptxtb->FIs(kclsTXRD))
		{
		fRet = ((PTXRD)_ptxtb)->FReplaceTxrd((PTXRD)ptxtb, 0, ccp,
			cp1, cp2 - cp1);
		}
	else
		{
		_EnsureChpIns();
		fRet = ((PTXRD)_ptxtb)->FReplaceTxtb(ptxtb, 0, ccp, cp1, cp2 - cp1,
			&_chpIns);
		}
	ReleasePpo(&ptxtb);

	if (fRet)
		{
		_ptxtb->BumpCombineUndo();
		cp1 += ccp;
		SetSel(cp1, cp1);
		ShowSel();
		}

	return fRet;
}


/***************************************************************************
	Apply the given character properties to the current selection.
***************************************************************************/
bool TXRG::FApplyChp(PCHP pchp, PCHP pchpDiff)
{
	AssertThis(0);
	AssertVarMem(pchp);
	AssertNilOrVarMem(pchpDiff);

	long cpMin, cpLim, cpAnchor, cpOther;
	ulong grfont;

	cpMin = LwMin(cpAnchor = _cpAnchor, cpOther = _cpOther);
	cpLim = LwMax(_cpAnchor, _cpOther);

	if (cpMin == cpLim)
		{
		if (pvNil == pchpDiff)
			{
			_chpIns = *pchp;
			_fValidChp = fTrue;
			return fTrue;
			}

		_EnsureChpIns();
		if (fontNil != (grfont = pchp->grfont ^ pchpDiff->grfont))
			_chpIns.grfont = (_chpIns.grfont & ~grfont) | (pchp->grfont & grfont);
		if (pchp->onn != pchpDiff->onn)
			_chpIns.onn = pchp->onn;
		if (pchp->dypFont != pchpDiff->dypFont)
			_chpIns.dypFont = pchp->dypFont;
		if (pchp->dypOffset != pchpDiff->dypOffset)
			_chpIns.dypOffset = pchp->dypOffset;
		if (pchp->acrFore != pchpDiff->acrFore)
			_chpIns.acrFore = pchp->acrFore;
		if (pchp->acrBack != pchpDiff->acrBack)
			_chpIns.acrBack = pchp->acrBack;
		AssertThis(0);
		return fTrue;
		}

	_SwitchSel(fFalse, ginNil);
	if (!((PTXRD)_ptxtb)->FApplyChp(cpMin, cpLim - cpMin, pchp, pchpDiff))
		{
		_SwitchSel(fTrue, kginMark);
		return fFalse;
		}
	_fValidChp = fFalse;
	SetSel(cpAnchor, cpOther);
	ShowSel();

	return fTrue;
}


/***************************************************************************
	Apply the given paragraph properties to the current selection.
***************************************************************************/
bool TXRG::FApplyPap(PPAP ppap, PPAP ppapDiff, bool fExpand)
{
	AssertThis(0);
	AssertVarMem(ppap);
	AssertNilOrVarMem(ppapDiff);
	long cpMin, cpLim, cpAnchor, cpOther;

	cpMin = LwMin(cpAnchor = _cpAnchor, cpOther = _cpOther);
	cpLim = LwMax(_cpAnchor, _cpOther);

	_SwitchSel(fFalse, ginNil);
	if (!((PTXRD)_ptxtb)->FApplyPap(cpMin, cpLim - cpMin, ppap, ppapDiff,
			pvNil, pvNil, fExpand))
		{
		_SwitchSel(fTrue, kginMark);
		return fFalse;
		}

	SetSel(cpAnchor, cpOther);
	ShowSel();

	return fTrue;
}


/***************************************************************************
	Apply a character or paragraph property
***************************************************************************/
bool TXRG::FCmdApplyProperty(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);
	CHP chpOld, chpNew;
	PAP papOld, papNew;
	long onn;
	STN stn;

	ClearPb(&papOld, size(PAP));
	papNew = papOld;

	_EnsureChpIns();
	chpOld = chpNew = _chpIns;

	switch (pcmd->cid)
		{
	case cidPlain:
		chpNew.grfont = fontNil;
		chpOld.grfont = (ulong)~fontNil;
		goto LApplyChp;
	case cidBold:
		chpNew.grfont ^= fontBold;
		goto LApplyChp;
	case cidItalic:
		chpNew.grfont ^= fontItalic;
		goto LApplyChp;
	case cidUnderline:
		chpNew.grfont ^= fontUnderline;
		goto LApplyChp;
	case cidChooseFont:
		if (pvNil == pcmd->pgg || pcmd->pgg->IvMac() != 1 ||
				!stn.FSetData(pcmd->pgg->QvGet(0), pcmd->pgg->Cb(0)))
			{
			break;
			}
		if (!vntl.FGetOnn(&stn, &onn))
			break;
		chpNew.onn = onn;
		chpOld.onn = ~onn;
		goto LApplyChp;
	case cidChooseSubSuper:
		// the amount to offset by is pcmd->rglw[0] ^ (1L << 31), so 0 can be
		// used to indicate that we're to ask the user.
		if (pcmd->rglw[0] == 0)
			{
			long dyp = _chpIns.dypOffset;

			// ask the user for the amount to sub/superscript by
			if (!_FGetOtherSubSuper(&dyp))
				return fTrue;
			pcmd->rglw[0] = dyp ^ (1L << 31);
			}
		chpNew.dypOffset = pcmd->rglw[0] ^ (1L << 31);
		chpOld.dypOffset = ~chpNew.dypOffset;
		goto LApplyChp;
	case cidChooseFontSize:
		if (pcmd->rglw[0] == 0)
			{
			long dyp = _chpIns.dypFont;

			// ask the user for the font size
			if (!_FGetOtherSize(&dyp))
				return fTrue;
			pcmd->rglw[0] = dyp;
			}
		if (!FIn(pcmd->rglw[0], 6, 256))
			return fTrue;
		chpNew.dypFont = pcmd->rglw[0];
		chpOld.dypFont = ~pcmd->rglw[0];
LApplyChp:
		FApplyChp(&chpNew, &chpOld);
		break;

	case cidJustifyLeft:
		papNew.jc = jcLeft;
		papOld.jc = jcLim;
		goto LApplyPap;
	case cidJustifyCenter:
		papNew.jc = jcCenter;
		papOld.jc = jcLim;
		goto LApplyPap;
	case cidJustifyRight:
		papNew.jc = jcRight;
		papOld.jc = jcLim;
		goto LApplyPap;
	case cidIndentNone:
		papNew.nd = ndNone;
		papOld.nd = ndLim;
		goto LApplyPap;
	case cidIndentFirst:
		papNew.nd = ndFirst;
		papOld.nd = ndLim;
		goto LApplyPap;
	case cidIndentRest:
		papNew.nd = ndRest;
		papOld.nd = ndLim;
		goto LApplyPap;
	case cidIndentAll:
		papNew.nd = ndAll;
		papOld.nd = ndLim;
LApplyPap:
		FApplyPap(&papNew, &papOld);
		break;
		}

	return fTrue;
}


/***************************************************************************
	Get a font size from the user.
***************************************************************************/
bool TXRG::_FGetOtherSize(long *pdypFont)
{
	AssertThis(0);
	AssertVarMem(pdypFont);

	TrashVar(pdypFont);
	return fFalse;
}


/***************************************************************************
	Get the amount to sub/superscript from the user.
***************************************************************************/
bool TXRG::_FGetOtherSubSuper(long *pdypOffset)
{
	AssertThis(0);
	AssertVarMem(pdypOffset);

	TrashVar(pdypOffset);
	return fFalse;
}


/***************************************************************************
	Apply a character or paragraph property
***************************************************************************/
bool TXRG::FSetColor(ACR *pacrFore, ACR *pacrBack)
{
	AssertThis(0);
	AssertNilOrPo(pacrFore, 0);
	AssertNilOrPo(pacrBack, 0);
	CHP chp, chpDiff;

	chp.Clear();
	chpDiff.Clear();

	if (pvNil != pacrFore)
		{
		chp.acrFore = *pacrFore;
		chpDiff.acrFore.SetToInvert();
		if (chpDiff.acrFore == chp.acrFore)
			chpDiff.acrFore.SetToClear();
		}
	if (pvNil != pacrBack)
		{
		chp.acrBack = *pacrBack;
		chpDiff.acrBack.SetToInvert();
		if (chpDiff. acrBack == chp.acrBack)
			chpDiff.acrBack.SetToClear();
		}

	return FApplyChp(&chp, &chpDiff);
}


/***************************************************************************
	Enable, check/uncheck property commands.
***************************************************************************/
bool TXRG::FEnablePropCmd(PCMD pcmd, ulong *pgrfeds)
{
	PAP pap;
	bool fCheck;
	STN stn, stnT;

	_EnsureChpIns();
	((PTXRD)_ptxtb)->FetchPap(LwMin(_cpAnchor, _cpOther), &pap);

	switch (pcmd->cid)
		{
	case cidPlain:
		fCheck = (_chpIns.grfont == fontNil);
		break;
	case cidBold:
		fCheck = FPure(_chpIns.grfont & fontBold);
		break;
	case cidItalic:
		fCheck = FPure(_chpIns.grfont & fontItalic);
		break;
	case cidUnderline:
		fCheck = FPure(_chpIns.grfont & fontUnderline);
		break;

	case cidChooseFont:
		if (pvNil == pcmd->pgg || pcmd->pgg->IvMac() != 1)
			break;
		if (!stnT.FSetData(pcmd->pgg->PvLock(0), pcmd->pgg->Cb(0)))
			{
			pcmd->pgg->Unlock();
			break;
			}
		pcmd->pgg->Unlock();
		vntl.GetStn(_chpIns.onn, &stn);
		fCheck = stn.FEqual(&stnT);
		break;

	case cidChooseFontSize:
		fCheck = (_chpIns.dypFont == pcmd->rglw[0]);
		break;
	case cidChooseSubSuper:
		fCheck = (_chpIns.dypOffset == (pcmd->rglw[0] ^ (1L << 31)));
		break;

	case cidJustifyLeft:
		fCheck = (pap.jc == jcLeft);
		break;
	case cidJustifyCenter:
		fCheck = (pap.jc == jcCenter);
		break;
	case cidJustifyRight:
		fCheck = (pap.jc == jcRight);
		break;
	case cidIndentNone:
		fCheck = (pap.nd == ndNone);
		break;
	case cidIndentFirst:
		fCheck = (pap.nd == ndFirst);
		break;
	case cidIndentRest:
		fCheck = (pap.nd == ndRest);
		break;
	case cidIndentAll:
		fCheck = (pap.nd == ndAll);
		break;
		}
	*pgrfeds = fCheck ? fedsEnable | fedsCheck : fedsEnable | fedsUncheck;

	return fTrue;
}

